﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.DependencyInjection.Conditions;
using StyleMVVM.DependencyInjection.Impl;

namespace StyleMVVM.DependencyInjection
{
	public class ExportConfiguration<T> : IExportConfiguration<T>
	{
		private readonly List<Type> exportedTypes = new List<Type>();
		private readonly List<string> exportNames = new List<string>();
		private readonly IExportStrategy strategy;
		private CoreExportStartOrder? coreExportStart;
		private readonly List<ImportPropertyInfo> importProperties = new List<ImportPropertyInfo>();
		private readonly List<InitializePropertyInfo> initializeInfos = new List<InitializePropertyInfo>();
		private readonly List<ImportMethodInfo> importMethods = new List<ImportMethodInfo>();
		private readonly Dictionary<string, object> metadata = new Dictionary<string, object>();
		private readonly List<Tuple<EnrichWithDelegate, int>> enrichWithDelegates = new List<Tuple<EnrichWithDelegate, int>>();

		public ExportConfiguration(IExportStrategy strategy)
		{
			this.strategy = strategy;
		}

		public IExportConfiguration<T> WithPriority(int priority)
		{
			strategy.Priority = priority;

			return this;
		}

		public IExportConfiguration<T> As<TExportType>()
		{
			exportedTypes.Add(typeof(TExportType));

			return this;
		}

		public IExportConfiguration<T> InEnvironment(ExportEnvironment environment)
		{
			strategy.Environment = environment;

			return this;
		}

		public IExportConfiguration<T> AsName(string name)
		{
			exportNames.Add(name);

			return this;
		}

		public IExportConfiguration<T> AndShared()
		{
			strategy.LifeCycleContainer = new WeakSharedLifeCycleContainer();

			return this;
		}

		public IExportConfiguration<T> AndSharedPermenantly()
		{
			strategy.LifeCycleContainer = new PermanentlySharedLifeCycleContainer();

			return this;
		}

		public IExportConfiguration<T> AndSingleton()
		{
			strategy.LifeCycleContainer = new SingletonLifeCycleContainer();

			return this;
		}

		public IExportConfiguration<T> AndCachedWithAbsoluteExpiration(TimeSpan timeSpan)
		{
			strategy.LifeCycleContainer = new CachedLifeCycleContainer(false, timeSpan);

			return this;
		}

		public IExportConfiguration<T> AndCachedWithSlidingExpiration(TimeSpan timeSpan)
		{
			strategy.LifeCycleContainer = new CachedLifeCycleContainer(true, timeSpan);

			return this;
		}

		public IExportConfiguration<T> CoreExport(CoreExportStartOrder startOrder)
		{
			coreExportStart = startOrder;

			return this;
		}

		public IExportConfiguration<T> ImportDefaultConstructor()
		{
			foreach (ConstructorInfo declaredConstructor in strategy.ActivationType.GetTypeInfo().DeclaredConstructors)
			{
				IInstanceExportStrategy exportStrategy = strategy as IInstanceExportStrategy;

				if (exportStrategy != null)
				{
					exportStrategy.ConstructorInfo = declaredConstructor;
				}

				break;
			}

			return this;
		}

		public IExportConfiguration<T> ImportConstructor(Expression<Action> constructorExpression)
		{
			NewExpression newExpression = constructorExpression.Body as NewExpression;

			if (newExpression != null && strategy is IInstanceExportStrategy)
			{
				((IInstanceExportStrategy)strategy).ConstructorInfo = newExpression.Constructor;
			}

			return this;
		}

		public IExportConfiguration<T> InitializeProperty(Expression<Func<T, object>> property)
		{
			MemberExpression member = property.Body as MemberExpression;

			if (member == null)
			{
				throw new ArgumentException("property", "Property must be a property on type" + typeof(T).FullName);
			}

			if (strategy is IInstanceExportStrategy)
			{
				PropertyInfo propertyInfo =
					member.Member.DeclaringType.GetTypeInfo().GetDeclaredProperty(member.Member.Name);

				initializeInfos.Add(
					new InitializePropertyInfo
						{
							PropertyInfo = propertyInfo,
							InitializeValue = null
						});
			}

			return this;
		}

		public IExportConfiguration<T> InitializeProperty<TReturn>(Expression<Func<T, TReturn>> property, TReturn tReturn)
		{
			MemberExpression member = property.Body as MemberExpression;

			if (member == null)
			{
				throw new ArgumentException("property", "Property must be a property on type" + typeof(T).FullName);
			}

			if (strategy is IInstanceExportStrategy)
			{
				PropertyInfo propertyInfo =
					member.Member.DeclaringType.GetTypeInfo().GetDeclaredProperty(member.Member.Name);

				initializeInfos.Add(
					new InitializePropertyInfo
						{
							PropertyInfo = propertyInfo,
							InitializeValue = tReturn
						});
			}

			return this;
		}

		public IExportConfiguration<T> ImportProperty(Expression<Func<T, object>> property,
																	 bool isRequired = true,
																	 string exportName = null)
		{
			MemberExpression member = property.Body as MemberExpression;

			if (member == null)
			{
				throw new ArgumentException("property", "Property must be a property on type" + typeof(T).FullName);
			}

			if (strategy is IInstanceExportStrategy)
			{
				PropertyInfo propertyInfo =
					member.Member.DeclaringType.GetTypeInfo().GetDeclaredProperty(member.Member.Name);

				importProperties.Add(
					new ImportPropertyInfo
						{
							IsRequired = isRequired,
							PropertyInfo = propertyInfo,
							ExportName = exportName
						});
			}

			return this;
		}

		public IExportConfiguration<T> ImportProperty(Expression<Func<T, object>> property,
																	 IExportActivator exportActivator,
																	 bool isRequired = true)
		{
			MemberExpression member = property.Body as MemberExpression;

			if (member == null)
			{
				throw new ArgumentException("property", "Property must be a property on type" + typeof(T).FullName);
			}

			if (strategy is IInstanceExportStrategy)
			{
				PropertyInfo propertyInfo =
					member.Member.DeclaringType.GetTypeInfo().GetDeclaredProperty(member.Member.Name);

				importProperties.Add(
					new ImportPropertyInfo
						{
							IsRequired = isRequired,
							PropertyInfo = propertyInfo,
							ExportName = "",
							ExportActivator = exportActivator
						});
			}

			return this;
		}

		public IExportConfiguration<T> ImportMethod(Expression<Action<T>> method,
																  bool isRequired = true,
																  string exportName = null)
		{
			MethodCallExpression callExpression = method.Body as MethodCallExpression;

			if (callExpression != null)
			{
				ImportMethodInfo newInfo = new ImportMethodInfo
														{
															MethodName = callExpression.Method.Name
														};

				importMethods.Add(newInfo);
			}

			return this;
		}

		public IExportConfiguration<T> ActivationMethod(Expression<Action<T>> activationMethod)
		{
			MethodCallExpression callExpression = activationMethod.Body as MethodCallExpression;

			if (callExpression != null)
			{
				((IInstanceExportStrategy)strategy).ActivationMethod = callExpression.Method.Name;
			}
			return this;
		}

		public IExportConfiguration<T> LifeCycleContainer(ILifeCycleContainer container)
		{
			strategy.LifeCycleContainer = container;

			return this;
		}

		public IExportConfiguration<T> When(ExportConditionDelegate conditionDelegate)
		{
			strategy.AddCondition(new SimpleExportCondition(conditionDelegate));

			return this;
		}

		public IExportConfiguration<T> Unless(ExportConditionDelegate conditionDelegate)
		{
			// invers the delegate
			strategy.AddCondition(new SimpleExportCondition((x, y, z) => !conditionDelegate(x, y, z)));

			return this;
		}

		public IExportConfiguration<T> AndCondition(IExportCondition condition)
		{
			strategy.AddCondition(condition);

			return this;
		}

		public IExportConfiguration<T> WhenInjectedInto<TInjected>()
		{
			strategy.AddCondition(new WhenInjectedInto(typeof(TInjected)));

			return this;
		}

		public IExportConfiguration<T> WhenClassHas<TAttr>()
		{
			strategy.AddCondition(new WhenClassHas(typeof(TAttr)));

			return this;
		}

		public IExportConfiguration<T> WhenMemberHas<TAttr>()
		{
			strategy.AddCondition(new WhenMemberHas(typeof(TAttr)));

			return this;
		}

		public IExportConfiguration<T> WhenTargetHas<TAttr>()
		{
			strategy.AddCondition(new WhenTargetHas(typeof(TAttr)));

			return this;
		}

		public IExportConfiguration<T> AutoWireProperties(bool required = false)
		{
			Type testType = typeof(T);

			while (testType != null && testType != typeof(object))
			{
				foreach (PropertyInfo declaredProperty in testType.GetTypeInfo().DeclaredProperties)
				{
					importProperties.Add(new ImportPropertyInfo
													{
														PropertyInfo = declaredProperty,
														ExportName = "",
														IsRequired = required
													});
				}

				testType = testType.GetTypeInfo().BaseType;
			}

			return this;
		}

		public IExportConfiguration<T> DisposalCleanupDelegate(BeforeDisposalCleanupDelegate disposalCleanupDelegate)
		{
			strategy.DisposalCleanupDelegate = disposalCleanupDelegate;

			return this;
		}

		public IExportConfiguration<T> EnrichWith(EnrichWithDelegate enrichWithDelegate, int priority = 0)
		{
			enrichWithDelegates.Add(new Tuple<EnrichWithDelegate, int>(enrichWithDelegate,priority));

			return this;
		}

		public IExportConfiguration<T> WithMetadata(string metadataName, object objectValue)
		{
			metadata[metadataName] = objectValue;

			return this;
		}

		public IExportStrategy GetExportStrategy()
		{
			if (exportedTypes.Count > 0)
			{
				strategy.ExportedTypes = exportedTypes;

				foreach (Type exportedType in exportedTypes)
				{
					exportNames.Add(exportedType.FullName);
				}
			}

			if (exportNames.Count > 0)
			{
				strategy.ExportNames = exportNames;
			}

			if (strategy.ExportNames == null && strategy.ExportedTypes == null)
			{
				strategy.ExportNames = new List<string> { strategy.ActivationType.Name };
			}

			if (metadata.Count > 0)
			{
				strategy.Metadata = metadata;
			}

			if (enrichWithDelegates.Count > 0)
			{
				strategy.EnrichWithDelegates = 
					enrichWithDelegates.OrderByDescending(x => x.Item2).Select(x => x.Item1).ToArray();
			}

			if (strategy is IInstanceExportStrategy)
			{
				IInstanceExportStrategy instanceExportStrategy = strategy as IInstanceExportStrategy;

				instanceExportStrategy.ImportProperties = importProperties;
				instanceExportStrategy.InitializeProperties = initializeInfos;
				instanceExportStrategy.ImportMethods = importMethods;
			}

			return strategy;
		}
	}
}